/**
 * Copyright (c) 2016-2019 人人开源 All rights reserved.
 *
 * https://www.renren.io
 *
 * 版权所有，侵权必究！
 */

package demo.controller;
import demo.certificate.CerLoad;
import demo.certificate.PKCS12LoadImp;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.UaClient;
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig;
import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
import org.eclipse.milo.opcua.stack.client.UaTcpStackClient;
import org.eclipse.milo.opcua.stack.core.AttributeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.*;
import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest;
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters;
import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
import org.springframework.web.bind.annotation.*;

import java.io.File;
import java.security.KeyPair;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;

import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;

/**
 * 系统用户
 *
 * @author Mark sunlightcs@gmail.com
 */
@RestController
@RequestMapping("/test")
public class TestController{

	/**
	 * 注意：
	 * 1.
	 * 问题：如果采集程序 和 OPC-Server 不在一台服务器上 注意要配置 本地域名解析
	 * 原因：如果是不是在linux 上的 那么 要修改本地域名解析，（因为设备端开的是虚拟机启动的ip是DESKTOP-5CTK5AA（192.168.0.163））
	 */
	@GetMapping("/testNginx")
	public Integer testNginx() throws Exception {
		String EndPointUrl = "opc.tcp://192.168.0.163:53530/OPCUA/SimulationServer";

		File cerfile = new File("slef-client3.pfx");
		//File cerfile = new File("/usr/local/upcua/slef-client3.pfx");

		CerLoad cerLoad = new PKCS12LoadImp();    //加载pfx证书文件的实例
		//CerLoad cerLoad = new DerPemLoadImp();    加载der和pem证书文件的实例
		X509Certificate certificate  =cerLoad.getCer(cerfile);  //获取证书对象
		KeyPair keyPair = cerLoad.getKeyPair(cerfile);          //获取密钥对 对象

		//搜索OPC节点
		EndpointDescription[] endpointDescriptions = null;
		System.out.println("开始OPC连接...");
		try{
			endpointDescriptions = UaTcpStackClient.getEndpoints(EndPointUrl).get();
			System.out.println(endpointDescriptions[1]);
		}catch (Throwable e){
			System.out.println("获取端点失败");
			return -1;
		}

		/**
		 * level -  mode
		 *  1       sign
		 *  0       None
		 *  2       SignAndEncrypt
		 * */
		EndpointDescription endpointDescription = endpointDescriptions[1];


		//创建OpcUaClientConfig 配置对象
		// 下面的配置信息要和 opc-server 有关
		OpcUaClientConfig config = OpcUaClientConfig.builder()
				.setApplicationName(LocalizedText.english("SimulationServer"))
				//注意这里配置的ApplicationUri 必须和 证书 的uri 相互对应
				.setApplicationUri("urn:192.168.0.163:SimulationServer:client")
				.setCertificate(certificate)
				.setKeyPair(keyPair)
				.setEndpoint(endpointDescription)
				// 如果要 账号密码 验证 必须先在OPC-SERVER上添加 账号密码
				.setIdentityProvider(new UsernameProvider("CXCX","251128856"))
				.setRequestTimeout(uint(5000))
				.build();

		//利用OpcUaClientConfig创建OPC UA客户端

		OpcUaClient opcClient = new OpcUaClient(config);



		//先连接
		UaClient uaClient = opcClient.connect().get();


		//写入值
		int v = 70;
		float a = (float) 10.11;

		NodeId nodeId = new NodeId(3,"Int32");
		Variant value = new Variant(v);

		//test float   begin
		NodeId nodeId2 = new NodeId(3,"Float");
		Variant value2 = new Variant(a);
		DataValue dataValue2 = new DataValue(value2,null,null);
		StatusCode statusCode2 = uaClient.writeValue(nodeId2,dataValue2).get();
		//test float   end

		new Thread(()->{
			try {
				createSubscription(opcClient);
			} catch (ExecutionException e) {
				e.printStackTrace();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

		}).start();

		Thread.sleep(2000);
		for(int i=0;i<10;i++){
			//Thread.sleep(new Random().nextInt(1200));
			Thread.sleep(1500);

			DataValue dataValue = new DataValue(value,null,null);

			//注意这里writeValue使用了异步模式，如果没有这个get()，在消息订阅出发的时候只能得到一两次消息更新
			StatusCode statusCode = uaClient.writeValue(nodeId,dataValue).get();
			if(statusCode.isGood()){
				v++;
				value = new Variant(v);
			}
		}
		// 压力测试  开始
		/*while (true){
			DataValue dataValue = new DataValue(value,null,null);
			StatusCode statusCode = uaClient.writeValue(nodeId,dataValue).get();
			if(statusCode.isGood()){
				v++;
				value = new Variant(v);
				DataValue values = uaClient.readValue(0.0, TimestampsToReturn.Both,nodeId).get();
				System.out.println(values.getValue().getValue());
			}
		}*/
		// 压力测试  结束



		return 1;

	}

	public void createSubscription(OpcUaClient client) throws ExecutionException, InterruptedException {

		//创建连接
		client.connect().get();

		//创建发布间隔1000ms的订阅对象
		UaSubscription subscription = client.getSubscriptionManager().createSubscription(1000.0).get();

		//创建订阅的变量
		NodeId nodeId = new NodeId(3,"Int32");
		ReadValueId readValueId = new ReadValueId(nodeId, AttributeId.Value.uid(),null,null);

		//创建监控的参数
		MonitoringParameters parameters = new MonitoringParameters(
				uint(1),
				//订阅的时间间隔，，如果plc 变量刷新评率小于1s。那么中间数据会丢失
				1000.0,     // sampling interval
				null,       // filter, null means use default
				uint(10),   // queue size
				true        // discard oldest
		);

		//创建监控项请求
		//该请求最后用于创建订阅。
		MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(readValueId, MonitoringMode.Reporting, parameters);

		List<MonitoredItemCreateRequest> requests = new ArrayList<>();
		requests.add(request);

		//创建监控项，并且注册 变量值 改变时候的 回调函数。
		List<UaMonitoredItem> items = subscription.createMonitoredItems(
				TimestampsToReturn.Both,
				requests,
				(item,id)->{
					item.setValueConsumer((itemA, value)->{
						System.out.println();
						System.err.println("data:"+new Date());
						System.err.println("currentTime:"+System.currentTimeMillis());
						System.err.println("nodeid :"+itemA.getReadValueId().getNodeId());
						System.err.println("value :"+value.getValue().getValue());
						System.out.println();
					});
				}
		).get();
	}




}